﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.IO;

namespace SystemHelperLibrary.StreamExt
{
	public class FifoStream : Stream
	{
		private const int BlockSize = 65536;
		private const int MaxBlocksInCache = (3 * 1024 * 1024) / BlockSize;

		private int m_Size;
		private int m_RPos;
		private int m_WPos;
		private Stack m_UsedBlocks = new Stack();
		private ArrayList m_Blocks = new ArrayList();

		private byte[] AllocBlock()
		{
			byte[] Result = null;
			Result = m_UsedBlocks.Count > 0 ? (byte[])m_UsedBlocks.Pop() : new byte[BlockSize];
			return Result;
		}

		private void FreeBlock(byte[] block)
		{
			if (m_UsedBlocks.Count < MaxBlocksInCache)
				m_UsedBlocks.Push(block);
		}

		private byte[] GetWBlock()
		{
			byte[] Result = null;
			if (m_WPos < BlockSize && m_Blocks.Count > 0)
				Result = (byte[])m_Blocks[m_Blocks.Count - 1];
			else
			{
				Result = AllocBlock();
				m_Blocks.Add(Result);
				m_WPos = 0;
			}
			return Result;
		}

		// Stream members
		public override bool CanRead
		{
			get { return true; }
		}

		public override bool CanSeek
		{
			get { return false; }
		}

		public override bool CanWrite
		{
			get { return true; }
		}

		public override long Length
		{
			get
			{
				lock (this)
					return m_Size;
			}
		}

		public override long Position
		{
			get { throw new InvalidOperationException(); }
			set { throw new InvalidOperationException(); }
		}

		public override void Close()
		{
			Flush();
		}

		public override void Flush()
		{
			lock (this)
			{
				foreach (byte[] block in m_Blocks)
					FreeBlock(block);
				m_Blocks.Clear();
				m_RPos = 0;
				m_WPos = 0;
				m_Size = 0;
			}
		}

		public override void SetLength(long len)
		{
			throw new InvalidOperationException();
		}

		public override long Seek(long pos, SeekOrigin o)
		{
			throw new InvalidOperationException();
		}

		public override int Read(byte[] buf, int ofs, int count)
		{
			lock (this)
			{
				int Result = Peek(buf, ofs, count);
				Advance(Result);
				return Result;
			}
		}

		public override void Write(byte[] buf, int ofs, int count)
		{
			lock (this)
			{
				int Left = count;
				while (Left > 0)
				{
					int ToWrite = Math.Min(BlockSize - m_WPos, Left);
					Array.Copy(buf, ofs + count - Left, GetWBlock(), m_WPos, ToWrite);
					m_WPos += ToWrite;
					Left -= ToWrite;
				}
				m_Size += count;
			}
		}

		// extra stuff
		public int Advance(int count)
		{
			lock (this)
			{
				int SizeLeft = count;
				while (SizeLeft > 0 && m_Size > 0)
				{
					if (m_RPos == BlockSize)
					{
						m_RPos = 0;
						FreeBlock((byte[])m_Blocks[0]);
						m_Blocks.RemoveAt(0);
					}
					int ToFeed = m_Blocks.Count == 1 ? Math.Min(m_WPos - m_RPos, SizeLeft) : Math.Min(BlockSize - m_RPos, SizeLeft);
					m_RPos += ToFeed;
					SizeLeft -= ToFeed;
					m_Size -= ToFeed;
				}
				return count - SizeLeft;
			}
		}

		public int Peek(byte[] buf, int ofs, int count)
		{
			lock (this)
			{
				int SizeLeft = count;
				int TempBlockPos = m_RPos;
				int TempSize = m_Size;

				int CurrentBlock = 0;
				while (SizeLeft > 0 && TempSize > 0)
				{
					if (TempBlockPos == BlockSize)
					{
						TempBlockPos = 0;
						CurrentBlock++;
					}
					int Upper = CurrentBlock < m_Blocks.Count - 1 ? BlockSize : m_WPos;
					int ToFeed = Math.Min(Upper - TempBlockPos, SizeLeft);
					Array.Copy((byte[])m_Blocks[CurrentBlock], TempBlockPos, buf, ofs + count - SizeLeft, ToFeed);
					SizeLeft -= ToFeed;
					TempBlockPos += ToFeed;
					TempSize -= ToFeed;
				}
				return count - SizeLeft;
			}
		}

	}

	/// <summary>
	/// Stream utils class
	/// </summary>
	public static class StreamHelper
	{
		/// <summary>
		/// Copies one stream into a another one.
		/// </summary>
		/// <param name = "sourceStream">The source stream.</param>
		/// <param name = "targetStream">The target stream.</param>
		/// <param name = "bufferSize">The buffer size used to read / write.</param>
		/// <returns>The source stream.</returns>
		public static Stream CopyTo(this Stream sourceStream, Stream targetStream, int bufferSize)
		{
			if (sourceStream.CanRead == false)
				throw new InvalidOperationException("Source stream does not support reading.");
			if (targetStream.CanWrite == false)
				throw new InvalidOperationException("Target stream does not support writing.");

			var buffer = new byte[bufferSize];
			int bytesRead;

			while ((bytesRead = sourceStream.Read(buffer, 0, bufferSize)) > 0)
				targetStream.Write(buffer, 0, bytesRead);
			return sourceStream;
		}

		/// <summary>
		/// Copies any stream into a local MemoryStream
		/// </summary>
		/// <param name = "sourceStream">The source stream.</param>
		/// <returns>The copied memory stream.</returns>
		public static MemoryStream CopyToMemory(this Stream sourceStream)
		{
			MemoryStream memoryStream = new MemoryStream((int)sourceStream.Length);
			sourceStream.CopyTo(memoryStream);
			return memoryStream;
		}

		/// <summary>
		/// Reads the entire stream and returns a byte array.
		/// </summary>
		/// <param name = "sourceStream">The stream.</param>
		/// <returns>The byte array</returns>
		public static byte[] ReadAllBytes(this Stream sourceStream)
		{
			using (var memoryStream = sourceStream.CopyToMemory())
				return memoryStream.ToArray();
		}

		/// <summary>
		/// Copy data from one stream to another
		/// </summary>
		/// <param name="source"></param>
		/// <param name="destination"></param>
		static public void CopyStream(Stream source, Stream destination)
		{
			byte[] buffer = new byte[32768];
			int bytesRead;
			do
			{
				bytesRead = source.Read(buffer, 0, buffer.Length);
				destination.Write(buffer, 0, bytesRead);
			}
			while (bytesRead != 0);
		}

		public static string ReadAllAsString(this Stream stream)
		{
			using (var sr = new StreamReader(stream))
			{
				return sr.ReadToEnd();
			}
		}

		public static byte[] ReadAllAsBytes(this Stream stream)
		{
			byte[] buffer = new byte[16 * 1024];
			using (MemoryStream ms = new MemoryStream())
			{
				int read;
				while ((read = stream.Read(buffer, 0, buffer.Length)) > 0)
				{
					ms.Write(buffer, 0, read);
				}
				return ms.ToArray();
			}
		}

	}
}
